
lib file = <file.ssz>;
lib s = <string.ssz>;
lib consts = <consts.ssz>;
lib tbl = <table.ssz>;
lib mat = <math.ssz>;
lib sdl = <alpha/sdlplugin.ssz>;
lib com = "common.ssz";
lib m = <math.ssz>;
lib ogg = <alpha/ogg.ssz>;

public &SndNnm
{
  public int group = -1, number = 0;
}

public &Wave
{
  public uint samplesPerSec = 11025;
  public ushort channels = 0x1, bytesPerSample = 0x1;
  public ^ubyte wav;
  public &.SndNnm num;
}

public &Snd
{
  &.tbl.IntTable!ulong, &.Wave? soundTable;
  ushort ver, ver2;
  public void clear()
  {
    `soundTable.clear();
  }
  public ^/char loadFile(^/char filename)
  {
    ^/char error;
    &.file.File f;
    if(!f.open(filename, "rb")) ret .com.FileOpenError;
    `clear();
    uint subHeaderOffset, subFileLenght;
    uint numberOfSounds;
    ^/char wav(&.Wave wav=)
    {
      uint riffSize = 0x0, fmtSize = 0x0, dataSize = 0x0, musi;
      ubyte ub;
      loop{index i = 0; %char s; do:
        if(!`f.read!ubyte?(ub=)) ret .com.FileReadError;
        s .= (char)ub;
      while ++i < 4:
        if(!.s.equ(s, "RIFF")) ret "RIFFじゃない";
      }
      if(!`f.read!uint?(riffSize=)) ret .com.FileReadError;
      riffSize += 0x8;
      loop{index i = 0; %char s; do:
        if(!`f.read!ubyte?(ub=)) ret .com.FileReadError;
        s .= (char)ub;
      while ++i < 4:
        if(!.s.equ(s, "WAVE")) ret "";
      }
      loop{
        ushort fmtID, mushi;
        %char s;
        long ofs = (long)`subHeaderOffset + 28;
        uint size;
        while;
      do:
        s.new(0);
        loop{index i = 0; do:
          if(!`f.read!ubyte?(ub=)) break, break;
          s .= (char)ub;
        while ++i < 4:
        }
        if(!`f.read!uint?(size=)) break;
        branch{
        cond .s.equ(s, "fmt "):
          fmtSize = size;
          if(!`f.read!ushort?(fmtID=)) ret .com.FileReadError;
          if(fmtID != 0x1) ret "リニアPCMじゃない";
          if(!`f.read!ushort?(wav.channels=)) ret .com.FileReadError;
          if(wav.channels-0x1 >= 0x2) ret "チャンネル数が不正";
          if(!`f.read!uint?(wav.samplesPerSec=)) ret .com.FileReadError;
          if(wav.samplesPerSec-0x1 >= 0x100000){
            ret "周波数が不正 " + ''(int)wav.samplesPerSec;
          }
          if(!`f.read!uint?(musi=)) ret .com.FileReadError;
          if(!`f.read!ushort?(mushi=)) ret .com.FileReadError;
          if(!`f.read!ushort?(wav.bytesPerSample=)) ret .com.FileReadError;
          if(wav.bytesPerSample != 0x8 && wav.bytesPerSample != 0x10){
            ret "bit数が不正";
          }
          wav.bytesPerSample >>= 0x3;
        cond .s.equ(s, "data"):
          dataSize = size;
          wav.wav.new((index)dataSize);
          `f.readAry!ubyte?(wav.wav);
        }
        ofs += (long)size + 8;
        `f.seek(ofs, .file.Seek::SET);
      while
        (fmtSize == 0x0 || dataSize == 0x0)
        && ofs < (long)`subHeaderOffset + (long)riffSize + 16
        :
      }
      if(fmtSize == 0x0 && dataSize > 0x0) ret "fmt がない";
      ret "";
    }
    ubyte ub;
    loop{
      index i = 0;
      %char s;
    do:
      if(!f.read!ubyte?(ub=)) ret .com.FileReadError;
      s .= (char)ub;
      i++;
    while i < 12:
      if(!.s.equ(s, "ElecbyteSnd"\0)) ret "ElecbyteSndじゃない";
    }
    if(!f.read!ushort?(`ver=)) ret .com.FileReadError;
    if(!f.read!ushort?(`ver2=)) ret .com.FileReadError;
    if(!f.read!uint?(numberOfSounds=)) ret .com.FileReadError;
    if(!f.read!uint?(subHeaderOffset=)) ret .com.FileReadError;
    loop{
      index i = 0;
      uint nextSubHeaderOffset;
      ^&.Wave soundList;
      soundList.new((index)numberOfSounds);
    do:
      f.seek((long)subHeaderOffset, .file.Seek::SET);
      if(!f.read!uint?(nextSubHeaderOffset=)) ret .com.FileReadError;
      if(!f.read!uint?(subFileLenght=)) ret .com.FileReadError;
      if(!f.read!int?(soundList[i].num.group=)) ret .com.FileReadError;
      if(!f.read!int?(soundList[i].num.number=)) ret .com.FileReadError;
      if(soundList[i].num.group >= 0 && soundList[i].num.number >= 0){
        `soundTable.operate(
          (ulong)soundList[i].num.group<<0d32
          | (uint)soundList[i].num.number,
          [void(^&.Wave w=){
            if(#w > 0) ret;
            error = wav(soundList[i]=);
            w = soundList[i..i+1];
          }]);
        if(#error > 0) ret error;
      }
      subHeaderOffset = nextSubHeaderOffset;
      i++;
    while i < (index)numberOfSounds:}
    ret "";
  }
  public ^&.Wave getSound(int group, int number)
  {
    ret `soundTable.get((ulong)group<<0d32 | (uint)number);
  }
}

public &Sound
{
  public ^&.Wave sound;
  public ^/float chrx;
  public float x = 0.0;
  public short volume = 256;
  public bool loop_ = false, lowpriority = false;
  public float freqmul = 1.0;
  public float fidx = 0.0;
  const float frametime = 1.0 / (float).sdl.SNDFREQ;
  public void setVol(int v)
  {
    branch{
    cond v < 0: `volume = 0;
    cond v > 512: `volume = 512;
    else: `volume = v;
    }
  }
  public void setPan(float p)
  {
    if(#`sound > 0 ){
      `x = p;
    }
    branch{
    cond p < -160.0: `x = -160.0;
    cond p > 160.0: `x = 160.0;
    else: `x = p;
    }
  }
  public void setDefaultParameter()
  {
    `setVol(256);
    `loop_ = false;
    `lowpriority = false;
    `setPan(0.0);
    `freqmul = 1.0;
    `fidx = 0.0;
  }
  void mix_s16(^int buf, float fidxadd, int lv, int rv)
  {
    loop{
      index i = 0;
      int iidx;
      ^/ubyte w = `sound~wav;
      index l = #w;
      continue;
    do:
      buf[i] += ((int)w[iidx] | (int)(byte)w[iidx+1]<<8)*lv >> 8;
      buf[i+1] += ((int)w[iidx+2] | (int)(byte)w[iidx+3]<<8)*rv >>8;
      i += 2;
      `fidx += fidxadd;
    continue:
      iidx = (int)`fidx * 4;
      if(iidx >= l){
        if(!`loop_){
          `sound.new(0);
          ret;
        }
        iidx = 0;
        `fidx = 0.0;
      }
    while i < .sdl.SNDBUFLEN:
    }
  }
  void mix_m16(^int buf, float fidxadd, int lv, int rv)
  {
    loop{
      index i = 0;
      int iidx;
      ^/ubyte w = `sound~wav;
      index l = #w;
      int tmp;
      continue;
    do:
      buf[i] += (tmp = (int)w[iidx] | (int)(byte)w[iidx+1]<<8)*lv >> 8;
      buf[i+1] += tmp*rv >>8;
      i += 2;
      `fidx += fidxadd;
    continue:
      iidx = (int)`fidx * 2;
      if(iidx >= l){
        if(!`loop_){
          `sound.new(0);
          ret;
        }
        iidx = 0;
        `fidx = 0.0;
      }
    while i < .sdl.SNDBUFLEN:}
  }
  void mix_s8(^int buf, float fidxadd, int lv, int rv)
  {
    loop{
      index i = 0;
      int iidx;
      ^/ubyte w = `sound~wav;
      index l = #w;
      continue;
    do:
      buf[i] += ((int)w[iidx] - 128)*lv;
      buf[i+1] += ((int)w[iidx+1] - 128)*rv;
      i += 2;
      `fidx += fidxadd;
    continue:
      iidx = (int)`fidx * 2;
      if(iidx >= l){
        if(!`loop_){
          `sound.new(0);
          ret;
        }
        iidx = 0;
        `fidx = 0.0;
      }
    while i < .sdl.SNDBUFLEN:}
  }
  void mix_m8(^int buf, float fidxadd, int lv, int rv)
  {
    loop{
      index i = 0;
      int iidx;
      ^/ubyte w = `sound~wav;
      index l = #w;
      int tmp;
      continue;
    do:
      buf[i] += (tmp = (int)w[iidx] - 128)*lv;
      buf[i+1] += tmp*rv;
      i += 2;
      `fidx += fidxadd;
    continue:
      iidx = (int)`fidx;
      if(iidx >= l){
        if(!`loop_){
          `sound.new(0);
          ret;
        }
        iidx = 0;
        `fidx = 0.0;
      }
    while i < .sdl.SNDBUFLEN:}
  }
  public void mix(^int buf, float left, float right)
  {
    if(#`sound == 0) ret;
    float fidxadd = (float)`sound~samplesPerSec * `freqmul * `frametime;
    if(!.mat.isfinite(fidxadd) || fidxadd <= 0.0){
      `sound.new(0);
      ret;
    }
    int lv = `volume, rv = `volume;
    branch{
    cond `sound~channels == 0d2:
      branch{
      cond `sound~bytesPerSample == 0d2:
        `mix_s16(buf, fidxadd, lv, rv);
      else:
        `mix_s8(buf, fidxadd, lv, rv);
      }
    else:
      branch{
      cond `sound~bytesPerSample == 0d2:
        `mix_m16(buf, fidxadd, lv, rv);
      else:
        `mix_m8(buf, fidxadd, lv, rv);
      }
    }
  }
}

public &Bgm
{
  &Vorbis
  {
    public ^&.ogg.OggVorbis ov;
    /^short buf;
    /index bufOffset = 0, bufLength = 0;
    public void clear()
    {
      if(#`ov > 0){
        .sdl.sendCloseBGM();
        `ov.new(0);
      }
    }
    public bool open(^/char file)
    {
      `ov.new(1);
      if(#`buf == 0) `buf.new(4096);
      `bufOffset = `bufLength = 0;
      if(!`ov~open(file) || !.sdl.sendOpenBGM(`ov~rate(), `ov~channels())){
        `clear();
        ret false;
      }
      ret true;
    }
    public bool write()
    {
      if(#`ov == 0) ret false;
      if(`bufLength == 0){
        `bufOffset = 0;
        `bufLength = `ov~read(`buf);
        if(`bufLength == 0){
          `ov~seek(0.0);
          `bufLength = `ov~read(`buf);
        }
      }
      if(`bufLength < 0) ret false;
      index wlen = .sdl.sendWriteBGM(`buf[`bufOffset..`bufOffset+`bufLength]);
      `bufOffset += wlen;
      `bufLength -= wlen;
      ret wlen > 0;
    }
  }
  /^/char fileName;
  &Vorbis vor;
  public void play(^/char file)
  {
    `clear();
    branch{
    cond .s.equ(.s.toLower(file[#file-4 .. -1]), ".ogg"):
      if(`vor.open(file)) `fileName = file;
    else:
      if(.sdl.playBGM("plugins", file)) `fileName = file;
    }
  }
  public void write()
  {
    loop{do: while `vor.write():}
  }
  public void clear()
  {
    `fileName = "";
    `vor.clear();
  }
}
public ^&Bgm bgm.new(1);


public ^int sndbuf.new(.sdl.SNDBUFLEN);
/^&Sound sounds.new(16);

public void sndbufClear()
{
  loop{index i = 0; do:
    .sndbuf[i++] = 0;
  while  i < .sdl.SNDBUFLEN:}
}
sndbufClear();

public ^&Sound getChannel(int ch)
{
  index c = .m.min!int?(15, ch);
  if(c >= 0) ret .sounds[c..c+1];
  loop{index i = 15; do:
    if(#.sounds[i].sound == 0){
      .sounds[i].setDefaultParameter();
      ret .sounds[i..i+1];
    }
  while --i >= 0:}
  ret .consts.null!&.Sound?();
}

public void mixSounds()
{
  .bgm~write();
  loop{index i = 0; while; do:
    .sounds[i].mix(.sndbuf, -160.0, 160.0);
    i++;
  while i < #.sounds:}
}

public bool addWave(^&.Wave wav)
{
  if(#wav == 0) ret false;
  ^&.Sound c = .getChannel(-1);
  if(#c == 0) ret false;
  c~sound = wav;
  ret true;
}
public void playSound()
{
  if(.sdl.setSndBuf(.sndbuf)){
    .sndbufClear();
    .mixSounds();
  }
}

